using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Reflection;
using System.Linq;

namespace ReflectUtils
{
    static class StaticMethodInfoUtils
    {
        struct MethodId : IEquatable<MethodId>
        {
            public string typeName;
            public string methodName;
            public bool Equals(MethodId other)
            {
                return other.typeName == typeName && other.methodName == methodName;
            }
            public override int GetHashCode()
            {
                return typeName.GetHashCode() ^ methodName.GetHashCode();
            }
        }

        private static Dictionary<Type, Dictionary<MethodId, Delegate>> cachedDelegates = new Dictionary<Type, Dictionary<MethodId, Delegate>>();

        private static T CreateDelegate<T>(Type type, string method) where T : Delegate
        {
            MethodInfo invokeInfo = typeof(T).GetMethod("Invoke");
            var types = invokeInfo.GetParameters().Select(x => x.ParameterType).ToArray();

            var methodInfo = type.GetMethod(method, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Static, null, types, null);
            var f = Delegate.CreateDelegate(typeof(T), null, methodInfo);

            return (T)f;
        }

        public static T GetMethodDelegate<T>(this Type type, string method) where T : Delegate
        {
            var delegateType = typeof(T);
            var typeMethod = new MethodId() { typeName = type.FullName, methodName = method };
            if (cachedDelegates.TryGetValue(delegateType, out var d))
            {
                if (d.TryGetValue(typeMethod, out var dd))
                {
                    return (T)dd;
                }
                else
                {
                    var f = CreateDelegate<T>(type, method);
                    d.Add(typeMethod, f);
                    return f;
                }
            }
            else
            {
                Dictionary<MethodId, Delegate> dict = new Dictionary<MethodId, Delegate>();
                var f = CreateDelegate<T>(type, method);
                dict.Add(typeMethod, f);
                cachedDelegates.Add(delegateType, dict);
                return f;
            }
        }

        public static R TryInvokeFunc<R>(this Type type, string method)
        {
            return type.GetMethodDelegate<Func<R>>(method)();
        }

        public static R TryInvokeFunc<T1, R>(this Type type, string method, T1 t1)
        {
            return type.GetMethodDelegate<Func<T1, R>>(method)(t1);
        }

        public static R TryInvokeFunc<T1, T2, R>(this Type type, string method, T1 t1, T2 t2)
        {
            return type.GetMethodDelegate<Func<T1, T2, R>>(method)(t1, t2);
        }

        public static R TryInvokeFunc<T1, T2, T3, R>(this Type type, string method, T1 t1, T2 t2, T3 t3)
        {
            return type.GetMethodDelegate<Func<T1, T2, T3, R>>(method)(t1, t2, t3);
        }

        public static R TryInvokeFunc<T1, T2, T3, T4, R>(this Type type, string method, T1 t1, T2 t2, T3 t3, T4 t4)
        {
            return type.GetMethodDelegate<Func<T1, T2, T3, T4, R>>(method)(t1, t2, t3, t4);
        }

        public static R TryInvokeFunc<T1, T2, T3, T4, T5, R>(this Type type, string method, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
        {
            return type.GetMethodDelegate<Func<T1, T2, T3, T4, T5, R>>(method)(t1, t2, t3, t4, t5);
        }

        public static void TryInvokeAction(this Type type, string method)
        {
            type.GetMethodDelegate<Action>(method)();
        }

        public static void TryInvokeAction<T1>(this Type type, string method, T1 t1)
        {
            type.GetMethodDelegate<Action<T1>>(method)(t1);
        }

        public static void TryInvokeAction<T1, T2>(this Type type, string method, T1 t1, T2 t2)
        {
            type.GetMethodDelegate<Action<T1, T2>>(method)(t1, t2);
        }

        public static void TryInvokeAction<T1, T2, T3>(this Type type, string method, T1 t1, T2 t2, T3 t3)
        {
            type.GetMethodDelegate<Action<T1, T2, T3>>(method)(t1, t2, t3);
        }

        public static void TryInvokeAction<T1, T2, T3, T4>(this Type type, string method, T1 t1, T2 t2, T3 t3, T4 t4)
        {
            type.GetMethodDelegate<Action<T1, T2, T3, T4>>(method)(t1, t2, t3, t4);
        }

        public static void TryInvokeAction<T1, T2, T3, T4, T5>(this Type type, string method, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
        {
            type.GetMethodDelegate<Action<T1, T2, T3, T4, T5>>(method)(t1, t2, t3, t4, t5);
        }
    }


    static class InstanceMethodInfoUtils
    {
        struct MethodId : IEquatable<MethodId>
        {
            public object target;
            public string typeName;
            public string methodName;
            public bool Equals(MethodId other)
            {
                return other.typeName == typeName && other.methodName == methodName && target == other.target;
            }
            public override int GetHashCode()
            {
                return typeName.GetHashCode() ^ methodName.GetHashCode() ^ target.GetHashCode();
            }
        }

        private static Dictionary<Type, Dictionary<MethodId, Delegate>> cachedDelegates = new Dictionary<Type, Dictionary<MethodId, Delegate>>();

        private static T CreateDelegate<T>(Type type, object thiz, string method) where T : Delegate
        {
            MethodInfo invokeInfo = typeof(T).GetMethod("Invoke");
            var types = invokeInfo.GetParameters().Select(x => x.ParameterType).ToList();

            var methodInfo = type.GetMethod(method, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance, null, types.ToArray(), null);

            var f = Delegate.CreateDelegate(typeof(T), thiz, methodInfo);

            return (T)f;
        }

        public static T GetMethodDelegate<T>(this object thiz, string method) where T : Delegate
        {
            var thizType = thiz.GetType();
            var delegateType = typeof(T);
            var typeMethod = new MethodId() { target = thiz, typeName = thizType.FullName, methodName = method };// string.Format("{0}.{1}.{2}", thizType.FullName, method, thiz.GetHashCode().ToString());
            if (cachedDelegates.TryGetValue(delegateType, out var d))
            {
                if (d.TryGetValue(typeMethod, out var dd))
                {
                    return (T)dd;
                }
                else
                {
                    var f = CreateDelegate<T>(thizType, thiz, method);
                    d.Add(typeMethod, f);
                    return f;
                }
            }
            else
            {
                Dictionary<MethodId, Delegate> dict = new Dictionary<MethodId, Delegate>();
                var f = CreateDelegate<T>(thizType, thiz, method);
                dict.Add(typeMethod, f);
                cachedDelegates.Add(delegateType, dict);
                return f;
            }
        }

        public static R TryInvokeFunc<R>(this object type, string method)
        {
            return type.GetMethodDelegate<Func<R>>(method)();
        }

        public static R TryInvokeFunc<T1, R>(this object type, string method, T1 t1)
        {
            return type.GetMethodDelegate<Func<T1, R>>(method)(t1);
        }

        public static R TryInvokeFunc<T1, T2, R>(this object type, string method, T1 t1, T2 t2)
        {
            return type.GetMethodDelegate<Func<T1, T2, R>>(method)(t1, t2);
        }

        public static R TryInvokeFunc<T1, T2, T3, R>(this object type, string method, T1 t1, T2 t2, T3 t3)
        {
            return type.GetMethodDelegate<Func<T1, T2, T3, R>>(method)(t1, t2, t3);
        }

        public static R TryInvokeFunc<T1, T2, T3, T4, R>(this object type, string method, T1 t1, T2 t2, T3 t3, T4 t4)
        {
            return type.GetMethodDelegate<Func<T1, T2, T3, T4, R>>(method)(t1, t2, t3, t4);
        }

        public static R TryInvokeFunc<T1, T2, T3, T4, T5, R>(this object type, string method, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
        {
            return type.GetMethodDelegate<Func<T1, T2, T3, T4, T5, R>>(method)(t1, t2, t3, t4, t5);
        }

        public static void TryInvokeAction(this object thiz, string method)
        {
            thiz.GetMethodDelegate<Action>(method)();
        }

        public static void TryInvokeAction<T1>(this object thiz, string method, T1 t1)
        {
            thiz.GetMethodDelegate<Action<T1>>(method)(t1);
        }

        public static void TryInvokeAction<T1, T2>(this object thiz, string method, T1 t1, T2 t2)
        {
            thiz.GetMethodDelegate<Action<T1, T2>>(method)(t1, t2);
        }

        public static void TryInvokeAction<T1, T2, T3>(this object thiz, string method, T1 t1, T2 t2, T3 t3)
        {
            thiz.GetMethodDelegate<Action<T1, T2, T3>>(method)(t1, t2, t3);
        }

        public static void TryInvokeAction<T1, T2, T3, T4>(this object thiz, string method, T1 t1, T2 t2, T3 t3, T4 t4)
        {
            thiz.GetMethodDelegate<Action<T1, T2, T3, T4>>(method)(t1, t2, t3, t4);
        }

        public static void TryInvokeAction<T1, T2, T3, T4, T5>(this object thiz, string method, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
        {
            thiz.GetMethodDelegate<Action<T1, T2, T3, T4, T5>>(method)(t1, t2, t3, t4, t5);
        }
    }

    public static class ReflectUtils
    {
        #region  Instance Method
        public static void ReflectInvokeFunc<R>(this object thiz, string method, out R result)
        {
            result = thiz.TryInvokeFunc<R>(method);
        }

        public static void ReflectInvokeFunc<T, R>(this object thiz, string method, T arg, out R result)
        {
            result = thiz.TryInvokeFunc<T, R>(method, arg);
        }

        public static void ReflectInvokeFunc<T1, T2, R>(this object thiz, string method, T1 arg, T2 arg2, out R result)
        {
            result = thiz.TryInvokeFunc<T1, T2, R>(method, arg, arg2);
        }

        public static void ReflectInvokeFunc<T1, T2, T3, R>(this object thiz, string method, T1 arg, T2 arg2, T3 arg3, out R result)
        {
            result = thiz.TryInvokeFunc<T1, T2, T3, R>(method, arg, arg2, arg3);
        }

        public static void ReflectInvokeFunc<T1, T2, T3, T4, R>(this object thiz, string method, T1 arg, T2 arg2, T3 arg3, T4 arg4, out R result)
        {
            result = thiz.TryInvokeFunc<T1, T2, T3, T4, R>(method, arg, arg2, arg3, arg4);
        }

        public static void ReflectInvokeAction(this object thiz, string method)
        {
            thiz.TryInvokeAction(method);
        }

        public static void ReflectInvokeAction<T1>(this object thiz, string method, T1 arg1)
        {
            thiz.TryInvokeAction<T1>(method, arg1);
        }

        public static void ReflectInvokeAction<T1, T2>(this object thiz, string method, T1 arg1, T2 arg2)
        {
            thiz.TryInvokeAction<T1, T2>(method, arg1, arg2);
        }

        public static void ReflectInvokeAction<T1, T2, T3>(this object thiz, string method, T1 arg1, T2 arg2, T3 arg3)
        {
            thiz.TryInvokeAction<T1, T2, T3>(method, arg1, arg2, arg3);
        }

        public static void ReflectInvokeAction<T1, T2, T3, T4>(this object thiz, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
        {
            thiz.TryInvokeAction<T1, T2, T3, T4>(method, arg1, arg2, arg3, arg4);
        }

        public static void ReflectInvokeAction<T1, T2, T3, T4, T5>(this object thiz, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
        {
            thiz.TryInvokeAction<T1, T2, T3, T4, T5>(method, arg1, arg2, arg3, arg4, arg5);
        }
        #endregion

        #region  Static 
        public static void ReflectInvokeStaticFunc<R>(this Type thiz, string method, out R result)
        {
            result = thiz.TryInvokeFunc<R>(method);
        }

        public static void ReflectInvokeStaticFunc<T, R>(this Type thiz, string method, T arg, out R result)
        {
            result = thiz.TryInvokeFunc<T, R>(method, arg);
        }

        public static void ReflectInvokeStaticFunc<T1, T2, R>(this Type thiz, string method, T1 arg, T2 arg2, out R result)
        {
            result = thiz.TryInvokeFunc<T1, T2, R>(method, arg, arg2);
        }

        public static void ReflectInvokeStaticFunc<T1, T2, T3, R>(this Type thiz, string method, T1 arg, T2 arg2, T3 arg3, out R result)
        {
            result = thiz.TryInvokeFunc<T1, T2, T3, R>(method, arg, arg2, arg3);
        }

        public static void ReflectInvokeStaticFunc<T1, T2, T3, T4, R>(this Type thiz, string method, T1 arg, T2 arg2, T3 arg3, T4 arg4, out R result)
        {
            result = thiz.TryInvokeFunc<T1, T2, T3, T4, R>(method, arg, arg2, arg3, arg4);
        }

        public static void ReflectInvokeStaticAction(this Type thiz, string method)
        {
            thiz.TryInvokeAction(method);
        }

        public static void ReflectInvokeStaticAction<T1>(this Type thiz, string method, T1 arg1)
        {
            thiz.TryInvokeAction<T1>(method, arg1);
        }

        public static void ReflectInvokeStaticAction<T1, T2>(this Type thiz, string method, T1 arg1, T2 arg2)
        {
            thiz.TryInvokeAction<T1, T2>(method, arg1, arg2);
        }

        public static void ReflectInvokeStaticAction<T1, T2, T3>(this Type thiz, string method, T1 arg1, T2 arg2, T3 arg3)
        {
            thiz.TryInvokeAction<T1, T2, T3>(method, arg1, arg2, arg3);
        }

        public static void ReflectInvokeStaticAction<T1, T2, T3, T4>(this Type thiz, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
        {
            thiz.TryInvokeAction<T1, T2, T3, T4>(method, arg1, arg2, arg3, arg4);
        }
        #endregion

        #region  旧版本
        public static object ReflectInvokeMethod(this object thiz, string method, params object[] args)
        {
            return thiz.GetType().InvokeMember(method, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance, null, thiz, args);
        }

        public static T ReflectInvokeMethod<T>(this object thiz, string method, params object[] args)
        {
            return (T)thiz.GetType().InvokeMember(method, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance, null, thiz, args);
        }

        public static object ReflectInvokeStaticMethod(this Type type, string method, params object[] args)
        {
            return type.InvokeMember(method, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Static, null, null, args);
        }

        public static T ReflectInvokeStaticMethod<T>(this Type type, string method, params object[] args)
        {
            return (T)type.InvokeMember(method, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Static, null, null, args);
        }

        public static T ReflectGetField<T>(this object thiz, string prop)
        {
            return (T)thiz.GetType().InvokeMember(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance, null, thiz, null);
        }

        public static T ReflectGetStaticField<T>(this Type type, string prop)
        {
            return (T)type.InvokeMember(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Static, null, null, null);
        }

        public static T ReflectGetProperty<T>(Type type, string prop, object thiz)
        {
            return (T)type.InvokeMember(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance, null, thiz, null);
        }

        public static T ReflectGetProperty<T>(this object thiz, string prop)
        {
            return (T)thiz.GetType().InvokeMember(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance, null, thiz, null);
        }

        public static T ReflectGetStaticProperty<T>(this Type type, string prop)
        {
            return (T)type.InvokeMember(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Static, null, null, null);
        }

        public static void ReflectSetField<T>(this Type type, string prop, object thiz, T value)
        {
            type.InvokeMember(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetField | BindingFlags.Instance, null, thiz, new object[] { value });
        }

        public static void ReflectSetField<T>(this object thiz, string prop, T value)
        {
            thiz.GetType().InvokeMember(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetField | BindingFlags.Instance, null, thiz, new object[] { value });
        }

        public static void ReflectSetStaticField<T>(this Type type, string prop, T value)
        {
            type.InvokeMember(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetField | BindingFlags.Static, null, null, new object[] { value });
        }

        public static void ReflectSetProperty<T>(this Type type, string prop, object thiz, T value)
        {
            type.InvokeMember(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, thiz, new object[] { value });
        }

        public static void ReflectSetProperty<T>(this object thiz, string prop, T value)
        {
            thiz.GetType().InvokeMember(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, thiz, new object[] { value });
        }

        public static void ReflectSetStaticProperty<T>(this Type type, string prop, T value)
        {
            type.InvokeMember(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Static, null, null, new object[] { value });
        }

        public static object ReflectCreateInstance(this Type type, params object[] args)
        {
            return type.InvokeMember(null,
                BindingFlags.DeclaredOnly |
                BindingFlags.Public | BindingFlags.NonPublic |
                BindingFlags.Instance | BindingFlags.CreateInstance, null, null, args);
        }
        #endregion
    }
}